import time
from abc import ABC, abstractmethod

from sqlalchemy import select, update

from forward.common.enums import LoginClientEnum, get_sex, NoticeEnum
from forward.config import get_settings
from forward.dependencies.database import db
from forward.exceptions.base import AppException
from forward.api.h5.schemas.user import (
    UserCenterOutSchema,
    UserInfoOutSchema,
    UserEditInSchema,
    UserChangePwdInSchema,
    UserBindMobileInSchema,
    UserSchema,
)
from forward.http_base import HttpResp
from forward.model.tables import UserModel, UserAuthModel
from forward.utils.config import ConfigUtil
from forward.utils.redis import RedisUtil
from forward.utils.tools import ToolsUtil
from forward.utils.urls import UrlUtil
from forward.utils.wechat import WeChatUtil


class UserService:
    """用户服务实现类"""

    async def center(self, user_id: int) -> UserCenterOutSchema:
        """个人中心"""
        user_record = await db.fetch_one(
            select(UserModel).where(UserModel.id == user_id)
        )
        res = UserCenterOutSchema.model_validate(user_record, from_attributes=True)
        if res.avatar:
            res.avatar = await UrlUtil.to_absolute_url(res.avatar)
        else:
            defaultAvatar = await ConfigUtil.get_val("user", "defaultAvatar", "")
            res.avatar = await UrlUtil.to_absolute_url(defaultAvatar or "")
        return res

    async def info(self, user_id: int) -> UserInfoOutSchema:
        """个人信息"""
        obj = await db.fetch_one(
            select(UserModel).where(UserModel.id == user_id).limit(1)
        )
        auth = await db.fetch_one(
            select(UserAuthModel).where(
                UserAuthModel.user_id == user_id,
                UserAuthModel.client == LoginClientEnum.MNP.value,
            )
        )
        user = UserSchema.model_validate(obj, from_attributes=True)
        res = UserInfoOutSchema.model_validate(obj, from_attributes=True)
        res.is_password = True if user.password else False
        res.is_bind_mnp = True if auth else False
        res.version = get_settings().version
        res.sex = get_sex(user.sex)
        if res.avatar:
            res.avatar = await UrlUtil.to_absolute_url(res.avatar)
        else:
            defaultAvatar = await ConfigUtil.get_val("user", "defaultAvatar", "")
            res.avatar = await UrlUtil.to_absolute_url(defaultAvatar or "")
        return res

    async def edit(self, user_id: int, edit_in: UserEditInSchema):
        """编辑信息"""
        field, value = edit_in.field.strip(), edit_in.value.strip()
        update_dict = {}
        if field == "avatar":
            update_dict["avatar"] = await UrlUtil.to_relative_url(value)
            await db.execute(
                update(UserModel).where(UserModel.id == user_id).values(**update_dict)
            )
        elif field == "username":
            obj = await db.fetch_one(
                select(UserModel).where(
                    UserModel.username == value, UserModel.is_delete == 0
                )
            )
            user = UserSchema.model_validate(obj, from_attributes=True)
            if obj and user.id != user_id:
                raise AppException(HttpResp.FAILED, msg="账号已被使用!")
            if obj and user.username == value:
                raise AppException(HttpResp.FAILED, msg="新账号与旧账号一致,修改失败!")
            update_dict["username"] = value
            await db.execute(
                update(UserModel).where(UserModel.id == user_id).values(**update_dict)
            )
        elif field == "nickname":
            update_dict["nickname"] = value
            await db.execute(
                update(UserModel).where(UserModel.id == user_id).values(**update_dict)
            )
        elif field == "sex":
            update_dict["sex"] = int(value) if value.isdigit() else 0
            await db.execute(
                update(UserModel).where(UserModel.id == user_id).values(**update_dict)
            )
        else:
            raise AppException(HttpResp.FAILED, msg="不被支持的类型")

    async def change_pwd(self, user_id: int, change_in: UserChangePwdInSchema):
        """修改密码"""
        obj = await db.fetch_one(
            select(UserModel).where(UserModel.id == user_id, UserModel.is_delete == 0)
        )
        assert obj, "用户不存在"
        user = UserSchema.model_validate(obj, from_attributes=True)
        if user.password:
            assert change_in.old_password, "oldPassword参数缺失"
            old_pwd = ToolsUtil.make_md5(change_in.old_password.strip() + user.salt)
            if old_pwd != user.password:
                raise AppException(HttpResp.FAILED, msg="原密码不正确!")
        salt = ToolsUtil.random_string(5)
        pwd = ToolsUtil.make_md5(change_in.password.strip() + salt)
        await db.execute(
            update(UserModel)
            .where(UserModel.id == user_id)
            .values(password=pwd, salt=salt)
        )

    async def bind_mobile(self, user_id: int, bind_in: UserBindMobileInSchema):
        """绑定手机"""
        type_code = (
            NoticeEnum.SMS_BIND_MOBILE_CODE.value
            if bind_in.type == "bind"
            else NoticeEnum.SMS_CHANGE_MOBILE_CODE.value
        )
        sms_code = await RedisUtil.get(
            f"{get_settings().redis_sms_code}{type_code}:{bind_in.mobile}"
        )
        if not (sms_code and sms_code == bind_in.code.lower()):
            raise AppException(HttpResp.FAILED, msg="验证码错误!")
        obj = await db.fetch_one(
            select(UserModel).where(
                UserModel.mobile == bind_in.mobile, UserModel.is_delete == 0
            )
        )
        assert obj, "用户不存在"
        user = UserSchema.model_validate(obj, from_attributes=True)
        if user.id == user_id:
            raise AppException(HttpResp.FAILED, msg="手机号已被其它账号绑定!")
        await db.execute(
            update(UserModel)
            .where(UserModel.id == user_id)
            .values(mobile=bind_in.mobile)
        )

    async def mnp_mobile(self, user_id: int, code: str):
        """微信手机号"""
        client = await WeChatUtil.mnp()
        try:
            data = client._post("wxa/business/getuserphonenumber", data={"code": code})
        except Exception as e:
            raise AppException(HttpResp.FAILED, msg=str(e))
        phone_info = data.get("phone_info", {})
        mobile = phone_info.get("phoneNumber", "")
        await db.execute(
            update(UserModel).where(UserModel.id == user_id).values(mobile=mobile)
        )

    @classmethod
    async def instance(cls):
        """实例化"""
        return cls()
